Skip to content

Keep page feedback from readers who decline cookies - #480

Open
eugenia-scandit wants to merge 11 commits into
mainfrom
feat/docs-feedback-no-consent
Open

eugenia-scandit wants to merge 11 commits into
mainfrom
feat/docs-feedback-no-consent

Conversation

@eugenia-scandit

@eugenia-scandit eugenia-scandit commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

The page feedback widget ("Was this page helpful?") sends votes and comments to PostHog, which only loads after a reader accepts cookies. A reader who declines and then writes a comment currently leaves no trace of it.

What

  • Fallback for refused captures: when PostHog refuses the event, the comment is posted to a configurable endpoint (an Apps Script Web App that appends it to a spreadsheet). It's sent only when PostHog refused, never alongside a successful capture, so a consenting reader's comment isn't recorded twice.
  • What it sends: the four fields the widget already sends to PostHog (page URL, page title, vote, comment). No identifier and nothing derived from the reader. A missing vote is sent as null, never as "not helpful".
  • How it's sent: no-cors with a text/plain body, because an Apps Script Web App doesn't answer a CORS preflight. The response is opaque, so this reports that the request was sent, not that it arrived. The widget's copy already promises only "sent".
  • Configuration: the endpoint and token come from DOCS_FEEDBACK_ENDPOINT and DOCS_FEEDBACK_TOKEN at build time (customFields in docusaurus.config.ts). Both default to empty, which disables the fallback; with them unset, the widget behaves exactly as today. The token is not a secret: it ships in the client bundle and only filters untargeted requests, so the receiving script must validate and rate-limit.

Before this does anything in production

  • The receiving Apps Script Web App must be deployed with anonymous access, and it should validate the payload and enforce a daily cap.
  • The production publishing job must pass its production-only values to the build as DOCS_FEEDBACK_ENDPOINT and DOCS_FEEDBACK_TOKEN; no other build sets them, and preview builds force them off. The CI variables DOCS_FEEDBACK_ENDPOINT_PROD and DOCS_FEEDBACK_TOKEN_PROD that job reads must exist before this merges.
  • Please confirm with whoever owns privacy and consent that storing anonymous comments without cookie consent is acceptable. No cookie or identifier is involved.

Checks

  • npm run build passes; tsc is clean.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
PR Preview Action v1.8.1

🚀 View preview at
https://Scandit.github.io/data-capture-documentation/pr-preview/pr-480/

Built to branch gh-pages at 2026-10-23 13:50 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

Comment thread src/components/PageFeedback/index.tsx Outdated
): Promise<boolean> {
if (!endpoint) return false;
try {
await fetch(endpoint, {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a privacy/consent review: the disclosure no longer matches where the data goes. For a reader who declined cookies, this sends the comment, page URL and title to script.google.com, and the request carries their IP and user agent. That's a different processor and different storage (a spreadsheet) than the consent banner covers. The widget's note (index.tsx:376) still says the comment is "sent to our analytics tool with this page's address", which is no longer accurate on this path.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partly addressed, still open: this needs a privacy/consent sign-off.

The disclosure now matches the destination (7c8da0b, 5507dce): on the fallback path, the note says the comment, the vote and the page's address and title go to a Google Apps Script form that adds them to a spreadsheet, and that the request carries the browser's IP address and user agent. On the analytics path, the note is unchanged.

What code can't settle: a reader who declines the cookie banner is exactly who the fallback sends to. That's the point of this PR, so someone responsible for privacy and consent needs to confirm it's acceptable before this ships.

Comment thread docusaurus.config.ts Outdated
// Set from the environment so a redeploy of the Web App (which changes the
// URL) needs no code change, and so a fork or a preview build does not
// silently post into the production sheet.
feedbackEndpoint: process.env.DOCS_FEEDBACK_ENDPOINT ?? "",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No build sets DOCS_FEEDBACK_ENDPOINT or DOCS_FEEDBACK_TOKEN. build-docs.yml (line 61) and docs-preview.yml run yarn build with no env block, so in production this is '', sendDirectFeedback returns false, and the fallback ships turned off. Please add the variables (as secrets or vars) to the deploy workflow, or this PR has no effect.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still open: the variables don't reach production yet.

I added DOCS_FEEDBACK_ENDPOINT (repo variable) and DOCS_FEEDBACK_TOKEN (repo secret) to the build step of build-docs.yml, for main only, and previews never get them (docs-preview.yml, and docusaurus.config.ts forces both to empty when preview_build is set). But build-docs.yml only builds and tests; nothing in this repo deploys the live site. So the pipeline that builds production must set these two variables too, or the fallback stays off. Who owns that pipeline? Until it's configured, merging this changes nothing for readers.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, and sorry: my original comment pointed you at the wrong workflow. docs.scandit.com is built and published by generate-docusaurus-documentation-production in the data-capture-sdk GitLab project, on an hourly schedule. It clones main here and runs npm run build.

I opened a GitLab MR that passes the two variables to that job only: https://gitlab.scandit.com/mobile/data-capture-sdk/-/merge_requests/39858

  • It maps DOCS_FEEDBACK_ENDPOINT_PROD and DOCS_FEEDBACK_TOKEN_PROD to the names docusaurus.config.ts reads. The staging job runs the same script, so it doesn't get them and can't post into the production sheet.
  • A GitLab maintainer still has to create those two CI/CD variables. Ideally do that before this PR merges: the production job skips the build while the docs commit is unchanged, so variables added later only take effect with the next commit to main.
  • The env: block in build-docs.yml doesn't affect production. Keeping it is fine, but the comment above it shouldn't suggest it's what turns the fallback on. Dropping it would also let you remove the GitHub repo variable and secret.

I'll leave this thread open until the GitLab side is configured.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reopening this one. @eugenia-scandit, I think cd2d304 should be reverted. It doesn't match how the merged GitLab MR (https://gitlab.scandit.com/mobile/data-capture-sdk/-/merge_requests/39858) works.

The MR doesn't give the docs build the _PROD names. It reads them and passes them on under the plain names, in the production job only:

# generate-docusaurus-documentation-production
DOCS_FEEDBACK_ENDPOINT: $DOCS_FEEDBACK_ENDPOINT_PROD
DOCS_FEEDBACK_TOKEN: $DOCS_FEEDBACK_TOKEN_PROD

The _PROD names exist so the docs build never reads them directly. The staging job (generate-docusaurus-documentation-staging) runs the same build script. If docusaurus.config.ts reads DOCS_FEEDBACK_ENDPOINT_PROD and the variables are created at project level, staging gets them too, and staging.docs.scandit.com posts into the production sheet. The MR's mapping would also have nothing left to read it.

With the config back on DOCS_FEEDBACK_ENDPOINT / DOCS_FEEDBACK_TOKEN (and build-docs.yml the same), it works as intended. Only production sees the values, and they can be masked project variables.

Still to do before this is done:

  • Revert cd2d304.
  • Create DOCS_FEEDBACK_ENDPOINT_PROD (the Web App /exec URL) and DOCS_FEEDBACK_TOKEN_PROD as CI/CD variables in mobile/data-capture-sdk. Neither exists yet. You have the values; @moritzhartmeier can add them. Ideally do this before this PR merges: the production job only rebuilds when the docs commit changes.

Comment thread src/components/PageFeedback/index.tsx
Comment thread src/components/PageFeedback/index.tsx Outdated
Comment thread src/components/PageFeedback/index.tsx Outdated
Comment thread src/components/PageFeedback/direct.ts
eugenia-scandit and others added 9 commits October 23, 2026 10:26
The feedback widget sends votes and comments to PostHog, which only loads
after a reader accepts cookies. A reader who declines and then writes a
comment currently leaves no trace of it.

When PostHog refuses the capture, the comment is now posted to a
configurable endpoint instead (an Apps Script Web App that appends it to
a spreadsheet). It is sent only when PostHog refused, never alongside a
successful capture, so nothing is recorded twice. The payload is the four
fields the widget already sends (URL, title, vote, comment), with no
identifier. The vote stays three-valued: a missing vote is sent as null,
not as "not helpful".

The endpoint and token come from DOCS_FEEDBACK_ENDPOINT and
DOCS_FEEDBACK_TOKEN at build time. Both default to empty, which disables
the fallback and leaves the widget behaving exactly as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The analytics helper now reports why a capture did not happen: PostHog
not loaded, the reader opted out, or PostHog threw. The boolean helper
other components use keeps its exact behaviour. The feedback widget posts
a comment to the fallback endpoint only when PostHog is not loaded; an
opted-out reader's comment is never sent anywhere.

The note under the comment box now says where a comment will go before
the reader sends it. When the fallback applies, it names the Google Apps
Script form and spreadsheet, the page address and title, and that the
request carries the browser's IP address and user agent.

Submitting is guarded against a second send while one is in flight, and
Send is marked disabled meanwhile. After the request settles, the
current outcome is read from a ref, and nothing updates the widget if it
has unmounted.

Failure and success copy follow the path taken: a PostHog refusal keeps
the "analytics is turned off or blocked" text; a failed fallback request
says the connection failed or was blocked. Because the fallback uses
no-cors and cannot see the response, its thanks says only that the note
was sent.

The endpoint and token are passed to the build of the main branch only.
Pull-request builds get empty values, and preview builds force the
fallback off in the config.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Otherwise the vote could also reach PostHog if the reader accepted
cookies a few seconds later, and be recorded twice.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The docs publishing script sets the production values as
DOCS_FEEDBACK_ENDPOINT_PROD and DOCS_FEEDBACK_TOKEN_PROD, so read those
names in docusaurus.config.ts and pass them under the same names in
build-docs.yml. Preview builds still leave them unset.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This reverts commit cd2d3046cb4a86c4c1de7ec62e42f198ea79056f.

The build reads the plain names. The production publishing job maps its
production-only values onto them, so a staging build of the same script
never sees them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… request

- The config and the CI workflow named the CI build as the one that sets
  the endpoint; it checks the site and publishes nothing. The comments
  now say the publishing job sets it.
- The token comment no longer claims the receiving end stops all abuse.
- The held vote is cleared before the fallback request rather than
  after, so a PostHog flush while the request is in flight cannot record
  the same vote twice; it is restored if the request fails.
- direct.ts: the body also carries the token. Small tidy-ups.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…while

Clearing the held vote before the fallback request left two gaps when
the request failed: the retry poll, which stops once it sees no held
vote, was never restarted; and a vote the reader cast during the request
(and PostHog then recorded) could be overwritten by the restored older
one and sent a second time with the opposite value. A vote counter now
decides whether to restore, and restoring restarts the poll.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A vote cast while the fallback request was in flight could disagree with
the one the request carried and be recorded in two places. The thumbs
are now aria-disabled and ignored during the send, like the Send button.
A vote restored after the widget unmounted mid-request gets one capture
attempt instead of being dropped.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@eugenia-scandit
eugenia-scandit force-pushed the feat/docs-feedback-no-consent branch from cd2d304 to 1a874cf Compare October 23, 2026 10:00
eugenia-scandit and others added 2 commits October 23, 2026 14:36
The capture helpers now sit on main's shared callPostHog (#484), and
translate its outcome (sent / unavailable / opted-out / failed) into the
four results the feedback fallback decides on. The fallback still fires
only when PostHog is not loaded. The workflow keeps main's C API test
step and this branch's build-step comment and values.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants